#include "qrcodecontroller.h"

#include <QMetaType>
#include <QDateTime>

QRCodeController::QRCodeController(QString camera_name, QObject *parent)
    : QObject(parent)
    , _camera_name(std::move(camera_name))
    , _camera_worker_thread(new QThread(this))
    , _detect_worker_thread(new QThread(this))
{
    qRegisterMetaType<QRCodeController::DetectResult>("QRCodeController::DetectResult");
    qRegisterMetaType<QRCodeController::State>("QRCodeController::State");

    _camera_worker = new QRCodeCameraWorker(_camera_name, nullptr);
    _camera_worker->moveToThread(_camera_worker_thread);
    connect(_camera_worker_thread, &QThread::finished, _camera_worker, &QObject::deleteLater);
    _camera_worker_thread->start();

    _detect_worker = new QRCodeDetectWorker(_camera_worker,
                                            R"(../QRCodeDemo/resource/opencv_3rdparty-wechat_qrcode/)",
                                            500,
                                            1.0,
                                            nullptr);
    _detect_worker->moveToThread(_detect_worker_thread);
    connect(_detect_worker_thread, &QThread::finished, _detect_worker, &QObject::deleteLater);
    connect(this, &QRCodeController::sg_startTimer, _detect_worker, &QRCodeDetectWorker::sl_startTimer);
    connect(this, &QRCodeController::sg_stopTimer, _detect_worker, &QRCodeDetectWorker::sl_stopTimer);
    connect(_detect_worker, &QRCodeDetectWorker::detected, this, [&](DetectResult result){
        Q_ASSERT(!result.empty());
        _last_valid_result = result;

        _state = State::CAMERA_OPENED_NOT_DETECTING;

        emit detected(_last_valid_result);
        emit sg_stopTimer();
    });

    _detect_worker_thread->start();
}

QRCodeController::~QRCodeController()
{
    _camera_worker_thread->quit();
    _detect_worker_thread->quit();
    _camera_worker_thread->wait(2000);
    _detect_worker_thread->wait(2000);
}

void QRCodeController::setCameraName(QString camera_name)
{
    _camera_name = std::move(camera_name);
    _camera_worker->setCameraName(_camera_name);
}

void QRCodeController::openCameraAndDetect(bool reopen_forced/*=false*/)
{
    if (!this->isCameraOpened() || reopen_forced) {
        _camera_worker->closeCamera();
        _camera_worker->openCamera();
    }

    if (_camera_worker->isCameraOpened()) {
        _state = State::CAMERA_OPENED_NOT_DETECTING;
    } else {
        _state = State::CAMERA_NOT_OPENED;
        return;
    }

    emit sg_startTimer();
    _state = State::CAMERA_DETECTING;
}

void QRCodeController::closeCamera()
{
    emit sg_stopTimer();
    _camera_worker->closeCamera();
    _state = State::CAMERA_NOT_OPENED;
}

cv::Mat QRCodeController::getCurrentCameraImageCVMat() const
{
    if (!this->isCameraOpened()) return {};

    return _camera_worker->getCurrentCameraImage();
}

QImage QRCodeController::getCurrentCameraImageQImage() const
{
    if (!this->isCameraOpened()) return {};

    auto frame = _camera_worker->getCurrentCameraImage();
    if (frame.empty()) return {};
    cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
    // after next line, the address inside cv::Mat `frame` will be deleted
    // since QImage also reference this address and is also default shallow copy(not deep copy)
    // explicitly call .copy() here to avoid segementation fault
    // And since here need to use deep copy, it's not recommended to use this function
    return QImage((const unsigned char*)(frame.data), frame.cols, frame.rows, QImage::Format_RGB888).copy();
}

QRCodeCameraWorker::QRCodeCameraWorker(QString camera_name, QObject *parent)
    : QObject(parent), _camera_name(std::move(camera_name))
{

}

cv::Mat QRCodeCameraWorker::getCurrentCameraImage() const
{
    cv::Mat frame;

    QMutexLocker locker(&_mutex); // 这儿的互斥锁比较耗时
    if (_cv_capture.isOpened()) {
        _cv_capture >> frame;
    }

    return frame;
}

void QRCodeCameraWorker::openCamera()
{
    QMutexLocker locker(&_mutex);

    if (_cv_capture.isOpened()) {
        _cv_capture.release(); // reopen
    }
    _cv_capture.open(_camera_name.toStdString());
    if (!_cv_capture.isOpened()) {
        _camera_opened = false;
        return;
    }

    _camera_opened = true;

    // setting
    // MJPG编码压缩,这个摄像头YUYV会识别不出
    _cv_capture.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M','J','P','G'));

    // 设置固定的分辨,帧率
    _cv_capture.set(cv::CAP_PROP_FRAME_WIDTH, _pixel_width);
    _cv_capture.set(cv::CAP_PROP_FRAME_HEIGHT, _pixel_height);
//    _cv_capture.set(cv::CAP_PROP_FRAME_WIDTH, 800);
//    _cv_capture.set(cv::CAP_PROP_FRAME_HEIGHT, 640);
    _cv_capture.set(cv::CAP_PROP_FPS, _fps);

    _cv_capture.setExceptionMode(false); // 不允许异常抛出
}


QRCodeDetectWorker::QRCodeDetectWorker(QRCodeCameraWorker* camera_worker,
                                       std::string model_dir/*=R"(../QRCodeDemo/resource/opencv_3rdparty-wechat_qrcode/)"*/,
                                       int cycle_timeout_msec/*=500*/,
                                       double scale_factor/*=1.0*/,
                                       QObject *parent/*=nullptr*/)
    : QObject(parent), _camera_worker(camera_worker), _timeout_msec(cycle_timeout_msec)
{

    _timer = new QTimer(this);

    _wechat_qr = std::make_shared<cv::wechat_qrcode::WeChatQRCode>(
                model_dir + "detect.prototxt",
                model_dir + "detect.caffemodel",
                model_dir + "sr.prototxt",
                model_dir + "sr.caffemodel"
                );

    _wechat_qr->setScaleFactor(scale_factor);

    connect(_timer, &QTimer::timeout, this, [&](){
        if (_camera_worker == nullptr) return;

        if (!_camera_worker->isCameraOpened()) {
            _timer->stop();
            return;
        }

        auto mat = _camera_worker->getCurrentCameraImage();
        if (mat.empty()) return;

        auto result = _detect(std::move(mat));

        if (result.empty()) {
            return;
        } else {
            emit detected(result);
            _timer->stop(); // stop timer after detected
        }
    });
}

QRCodeController::DetectResult QRCodeDetectWorker::_detect(cv::Mat mat)
{
    QRCodeController::DetectResult result;
    result.strs = _wechat_qr->detectAndDecode(mat, result.points);
    result.image = std::move(mat);

    return result;
}


